/*
    Dominate 3D - A cube-based game of life

    Copyright (C) 2007, Craig H. Miller (kidmosey@gmail.com)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "net.h"
#include "client.h"
#include "sharedobject.h"
#include "cube.h"
#include "player.h"
#include "util.h"
#include "option.h"
#include "thread.h"

#include <algorithm>
#include <functional>
#include <set>

namespace Server
{
    bool s_bGameStarted = false;
    SharedObject<Cube> g_Cube;

    void RemoveClient(const Client &cl);
    void SendMessageToAll(const TMessage type, const std::vector<SocketParam> &params);
    void SetCell(Cell *cell, Player *p, Cell::ESTATE state);
    void IncrementPlayer();
}

namespace NetFunctions
{
    void svGetPlayerList(Client &cl, const std::vector<SocketParam> &params)
    {
        Cube &cube = Server::g_Cube.Lock();

        std::vector<Player *>::iterator i = cube.m_listPlayers.begin();
        while (i != cube.m_listPlayers.end())
        {
            Player *player = *i;

            if (player->GetID() != cl.GetID())
            {
                SocketParam p[] =
                {
                    itoa(player->GetID()),
                    player->GetName(),
                    ftoa(player->c.r),
                    ftoa(player->c.g),
                    ftoa(player->c.b),
                    ftoa(player->c.a)
                };

                cl.SendMessage(CL_ADD_PLAYER, std::vector<SocketParam>(p, p+COUNT(p)));
            }

            *i++;
        }

        Server::g_Cube.Unlock();
    }

    void svGetCellList(Client &cl, const std::vector<SocketParam> &params)
    {
        Cube &cube = Server::g_Cube.Lock();

        std::vector<Cell *>::iterator i = cube.m_listCells.begin();
        while (i != cube.m_listCells.end())
        {
            Cell *cell = *i;

            if (!cell->IsEmpty())
            {
                SocketParam SC_params[] =
                {
                    itoa(cell->GetID()),
                    itoa(cell->GetPlayer()->GetID()),
                    itoa(cell->GetState())
                };
                cl.SendMessage(CL_SETCELL, std::vector<SocketParam>(SC_params, SC_params + COUNT(SC_params)));
            }

            *i++;
        }

        Server::g_Cube.Unlock();
    }

    void svSetPlayerName(Client &cl, const std::vector<SocketParam> &params)
    {
        assert(params.size() == 1);

        if (params.size() != 1) return;

        printf("SERVER[%d]: Renaming player to: %s\n", cl.GetSocket(), params[0].c_str());
        Player *player = Server::g_Cube.Lock().GetPlayer(cl.GetID());
        if (player) player->SetName(params[0]);
        Server::g_Cube.Unlock();

        // tell the client their color
        SocketParam p[] =
        {
            itoa(cl.GetID()),
            params[0]
        };
        Server::SendMessageToAll(CL_SET_PLAYER_NAME, std::vector<SocketParam>(p, p+COUNT(p)));
    }

    void svRegisterPlayer(Client &cl, const std::vector<SocketParam> &params)
    {
        assert(params.size() == 1);

        if (params.size() != 1) return;
        Cube &cube = Server::g_Cube.Lock();

        int numPlayers = cube.GetNumPlayers();
        if (!numPlayers) Server::s_bGameStarted = false;

        // if a game is in progress or ended
        if (Server::s_bGameStarted)
        {
            printf("SERVER[%d]: A game is currently in progress.\n", cl.GetSocket());
            cl.SendMessage(CL_SERVER_FULL, std::vector<SocketParam>());
            Server::RemoveClient(cl);

            Server::g_Cube.Unlock();
            return;
        }
        // if the max number of players hasn't been reached, yet.
        else if (numPlayers <= atoi(GetOption("NumPlayers").c_str()))
        {
            printf("SERVER[%d]: Registering the player.\n", cl.GetSocket());
            std::string name = params[0];
            Player *player = new Player(name);
            player->SetID(cl.GetID());

            //cl.SetPlayer(player);
            cube.AddPlayer(player);

            // tell the clients about this new player
            SocketParam p[] =
            {
                itoa(cl.GetID()),
                name,
                ftoa(player->c.r),
                ftoa(player->c.g),
                ftoa(player->c.b),
                ftoa(player->c.a)
            };
            Server::SendMessageToAll(CL_ADD_PLAYER, std::vector<SocketParam>(p, p+COUNT(p)));

            std::vector<Cell *> startCells = cube.GetStartCells(player);
            std::vector<Cell *>::iterator i = startCells.begin();
            while (i != startCells.end())
            {
                Server::SetCell(*i, player, (Cell::ESTATE)0);
                *i++;
            }
        }

        svGetPlayerList(cl, std::vector<SocketParam>());
        svGetCellList(cl, std::vector<SocketParam>());

        // Start the game!
        if (cube.GetNumPlayers() == atoi(GetOption("NumPlayers").c_str()))
        {
            Server::s_bGameStarted = true;
            Player *p = cube.m_listPlayers[0];
            cube.SetCurrentPlayer(p->GetID());

            SocketParam SCP_params[] =
            {
                itoa(p->GetID())
            };
            Server::SendMessageToAll(
                CL_SET_CURRENT_PLAYER,
                std::vector<SocketParam>(SCP_params, SCP_params + COUNT(SCP_params))
            );
        }

        Server::g_Cube.Unlock();
    }

    void svGetServerSettings(Client &cl, const std::vector<SocketParam> &params)
    {
        assert(params.size() == 0);

        if (params.size() != 0) return;

        printf("SERVER[%d]: Sending server settings to client.\n", cl.GetSocket());
        std::string p[] =
        {
            "CubeDimension",    GetOption("CubeDimension"),
            "NumPlayers",       GetOption("NumPlayers"),
            "Score_TotalCells", GetOption("Score_TotalCells"),
            "Score_TakenCells", GetOption("Score_TakenCells"),
            "Score_UseTimer",   GetOption("Score_UseTimer"),
            "Score_TimeLimit",  GetOption("Score_TimeLimit"),
            "Score_TimerBased", GetOption("Score_TimerBased")
        };
        cl.SendMessage(CL_SERVERSETTINGS, std::vector<SocketParam>(p, p+COUNT(p)));
    }

    void csDisconnect(Client &cl, const std::vector<SocketParam> &params)
    {
        Server::RemoveClient(cl);
    }

    void svPickCell(Client &cl, const std::vector<SocketParam> &params)
    {
        static Cell *s_selectedCell = 0;

        assert(params.size() == 1);

        if (params.size() != 1) return;
        Cube &cube = Server::g_Cube.Lock();

        printf("SERVER[%d]: Selecting cell %d.\n", cl.GetSocket(), atoi(params[0].c_str()));

        Player *player = cube.GetCurrentPlayer();
        if (!player || player->GetID() != cl.GetID())
        {
            Server::g_Cube.Unlock();
            return;
        }

        Cell *cell = cube.GetCell(atoi(params[0].c_str()));
        Cell *hlCell = 0;

        if (cell)
        {
            if (cell->GetPlayer() == cube.GetCurrentPlayer())
            {
                hlCell = cell;
            }
            else
            {
                if (s_selectedCell && cell->IsEmpty())
                {
                    switch (cell->GetState())
                    {
                    case Cell::JUMP_TO:
                        Server::SetCell(s_selectedCell, &Player::nullPlayer, (Cell::ESTATE)0);

                        // pass through
                    case Cell::COPY_TO:
                    {
                        Server::SetCell(cell, cube.GetCurrentPlayer(), (Cell::ESTATE)0);

                        // take over the surrounding cells
                        std::set<Cell *>::const_iterator i = cell->GetSurroundingCells().begin();
                        while (i != cell->GetSurroundingCells().end())
                        {
                            if (!(*i)->IsEmpty() &&
                                    (*i)->GetPlayer() != cube.GetCurrentPlayer())
                            {
                                Server::SetCell((*i), cube.GetCurrentPlayer(), (Cell::ESTATE)0);
                            }
                            *i++;
                        }

                        Server::IncrementPlayer();
                    }
                    break;

                    default:
                        break;
                    }
                }
            }
        }

        if (s_selectedCell)
        {
            s_selectedCell->Unselect();
            Server::SetCell(s_selectedCell, 0, Cell::NOT_HIGHLIGHTED);
        }

        if (hlCell)
        {
            hlCell->Select();
            Server::SetCell(hlCell, 0, Cell::SELECTED);
        }

        s_selectedCell = hlCell;
        Server::g_Cube.Unlock();
    }

    void csChatText(Client &cl, const std::vector<SocketParam> &params)
    {
        if (params.size() != 1) return;

        SocketParam CT_params[] =
        {
            itoa(cl.GetID()),
            params[0]
        };
        Server::SendMessageToAll(CS_CHATTEXT, std::vector<SocketParam>(CT_params, CT_params + COUNT(CT_params)));
    }
}

namespace
{
#define D(id, func) std::pair<TMessage, Client::MessageFunc>(id, func)
    const std::pair<TMessage, Client::MessageFunc> MESSAGEFUNC_PAIRS[] =
    {
        D(SV_SETPLAYERNAME, NetFunctions::svSetPlayerName),
        D(SV_REGISTER_PLAYER, NetFunctions::svRegisterPlayer),
        D(SV_GETSERVERSETTINGS, NetFunctions::svGetServerSettings),
        D(SV_PICKCELL, NetFunctions::svPickCell),
        D(SV_GETPLAYERLIST, NetFunctions::svGetPlayerList),
        D(CS_DISCONNECT, NetFunctions::csDisconnect),
        D(CS_CHATTEXT, NetFunctions::csChatText)
    };
    std::map<TMessage, Client::MessageFunc> MESSAGEFUNC(MESSAGEFUNC_PAIRS, MESSAGEFUNC_PAIRS + COUNT(MESSAGEFUNC_PAIRS));
#undef D
}

namespace Server
{
    typedef std::map<SOCKET, Client *> ClientMap;

    SOCKET m_Socket;
    SharedObject<ClientMap> m_mapClients;

    void IncrementPlayer()
    {
        Cube &cube = Server::g_Cube.Lock();

        Player *player = cube.GetCurrentPlayer(); // store the current player

        cube.IncrementPlayer();

        SocketParam SCP_params[] =
        {
            itoa((cube.GetCurrentPlayer()) ? cube.GetCurrentPlayer()->GetID() : 0)
        };
        Server::SendMessageToAll(
            CL_SET_CURRENT_PLAYER,
            std::vector<SocketParam>(SCP_params, SCP_params + COUNT(SCP_params))
        );

        if (player)
        {
            SocketParam SPS_params[] =
            {
                itoa(player->GetID()),
                itoa(player->m_taken),
                itoa(player->m_total),
                itoa(player->m_numTurns),
                ftoa(player->m_totalTime)
            };
            Server::SendMessageToAll(
                CL_SET_PLAYER_SCORE,
                std::vector<SocketParam>(SPS_params, SPS_params + COUNT(SPS_params))
            );
        }

        Server::g_Cube.Unlock();
    }

    void SetCell(Cell *cell, Player *p, Cell::ESTATE state)
    {
        if (p)
            cell->SetPlayer(p);

        if (state)
            cell->SetState(state);

        SocketParam params[] =
        {
            itoa(cell->GetID()),
            itoa((p)?p->GetID():-1),
            itoa(state)
        };
        SendMessageToAll(CL_SETCELL, std::vector<SocketParam>(params, params + COUNT(params)));
    }

    void DeleteClient(ClientMap::value_type &val)
    {
        printf("SERVER: Deleting client.\n");

        if (val.second)
        {
            Server::g_Cube.Lock().RemovePlayer(val.second->GetID());
            Server::g_Cube.Unlock();

            closesocket(val.first);

            delete val.second;
            val.second = 0;
        }

        printf("SERVER: Deleted client.\n");
    }


    void RemoveClient(const Client &cl)
    {
        printf("SERVER: Removing player from the game.\n");

        int clID = cl.GetID();

        ClientMap &cmap = m_mapClients.Lock();
        if (cmap.find(cl.GetSocket()) == cmap.end())
        {
            m_mapClients.Unlock();
            return;
        }

        DeleteClient(*cmap.find(cl.GetSocket()));
        //cmap.erase(cl.GetSocket());

        m_mapClients.Unlock();

        SocketParam RP_params[] = { itoa(clID) };
        SendMessageToAll(CL_REMOVE_PLAYER, std::vector<SocketParam>(RP_params, RP_params + COUNT(RP_params)));

        printf("SERVER: Removed  player from the game.\n");
    }

    void SendMessageToAll(const TMessage type, const std::vector<SocketParam> &params)
    {
        ClientMap &cmap = m_mapClients.Lock();

        ClientMap::iterator i = cmap.begin();
        while (i != cmap.end())
        {
            if ((*i).second)
                (*i).second->SendMessage(type, params);
            *i++;
        }

        m_mapClients.Unlock();
    }

    void AcceptClient(SOCKET sockfd)
    {
        timeval tv = { 0, 1 };
        fd_set serverSet = { 1, { sockfd } };

        if (select(0, &serverSet, 0, 0, &tv) == 1)
        {
            // accept socket
            struct sockaddr_in cli_addr;
            int clilen = sizeof(cli_addr);
            int newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);

            if (newsockfd != SOCKET_ERROR)
            {
                printf ("SERVER: New client - %d\n", newsockfd);

                // add new client to map
                m_mapClients.Lock()[newsockfd] = new Client(newsockfd);
                m_mapClients.Unlock();
            }
        }
    }

    bool ClientIsDead(ClientMap::value_type &val)
    {
        return (CheckReadStatus(val.first) == SOCKET_ERROR);
    }

    void CheckClients()
    {
        ClientMap &cmap = m_mapClients.Lock();

        // could std::map be more difficult please?
        // remove all dead clients from the client map
        ClientMap temp;
        std::remove_copy_if(cmap.begin(), cmap.end(), std::inserter(temp, temp.end()), ClientIsDead);
        cmap.swap(temp);

        m_mapClients.Unlock();
    }

    void ReadClient(const ClientMap::value_type &val)
    {
        SOCKET sockfd = val.first;

        if (CheckReadStatus(sockfd) == 1)
        {
            if (val.second)
                val.second->ReadMessage(MESSAGEFUNC);
        }
    }

    void ReadClients()
    {
        ClientMap &cmap = m_mapClients.Lock();
        std::for_each(cmap.begin(), cmap.end(), ReadClient);
        m_mapClients.Unlock();
    }

    class ServerObject: public Thread::ThreadObject
    {
    public:
        int m_port;
        ServerObject(int port): m_port(port)
        {
        }

        ~ServerObject()
        {
            closesocket(m_Socket);

            // reset all variables
            Server::m_Socket = INVALID_SOCKET;
            Server::g_Cube.Reset(new Cube());
            Server::m_mapClients.Reset(new ClientMap());
        }

        int Initialize()
        {
            // create our server socket
            m_Socket = socket(AF_INET, SOCK_STREAM, 0);
            if (m_Socket == (SOCKET )SOCKET_ERROR)
            {
                printf("SERVER ERROR opening socket: %d\n", WSAGetLastError());
                return -1;
            }

            // Initialize the listening socket
            struct sockaddr_in serv_addr;
            memset((char *) &serv_addr, 0, sizeof(serv_addr));

            serv_addr.sin_family = AF_INET;
            serv_addr.sin_port = htons(m_port);
            serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

            if (bind(m_Socket, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)
            {
                printf("SERVER ERROR binding socket: %d\n", WSAGetLastError());
                return -1;
            }

            if (listen(m_Socket, 5) == SOCKET_ERROR)
            {
                printf("SERVER ERROR opening listening port: %d\n", WSAGetLastError());
                return -1;
            }
            else
                printf("SERVER: Listening on port: %d\n", m_port);

            return 0;
        }

        void Run()
        {
            if (Initialize() < 0)
                return;

            m_mapClients.Reset(new ClientMap());

            // Initialize our cube
            printf("SERVER: Generating the cube...");
            Server::g_Cube.Reset(new Cube(atoi(GetOption("CubeDimension").c_str())));
            printf("done\n");

            printf("SERVER: Starting server loop.\n");
            while (!m_finished && CheckReadStatus(m_Socket) != SOCKET_ERROR)
            {
                AcceptClient(m_Socket);

                // check for incoming messages
                ReadClients();
                CheckClients();

                // check the timer (could also do this with a timer event)
                if (Server::s_bGameStarted && OptionIsEnabled("Score_UseTimer"))
                {
                    float timeLimit   = (float)atoi(GetOption("Score_TimeLimit").c_str());
                    float timeElapsed = Server::g_Cube.Lock().GetTimeElapsed();
                    Server::g_Cube.Unlock();

                    if (timeElapsed >= timeLimit)
                        Server::IncrementPlayer();
                }

                Sleep(1);
            }
            printf("SERVER: Exiting.\n");

            ClientMap &cmap = m_mapClients.Lock();
            std::for_each(cmap.begin(), cmap.end(), DeleteClient);
            m_mapClients.Unlock();
        }
    };

    Thread *m_ServerThread;
    void StartServerThread(int port)
    {
        m_ServerThread = new Thread(new ServerObject(port));
    }

    void EndServerThread()
    {
        delete m_ServerThread;
        m_ServerThread = 0;
    }
}
